package com.stylesmile.socket.util;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

public class FastUtil {
    protected static final TimeZone GMT = TimeZone.getTimeZone("GMT");
    protected static int MAX_HEADER_SIZE = 8192;
    /**
     * 必须支持SimpleDateFormat兼容的日期格式。
     * 注意，所有生成的日期字段必须仅为RFC 1123格式，
     * 而另一些则由接收方支持以实现向后兼容性。
     */
    public static final String[] DATE_PATTERNS = {
            "EEE, dd MMM yyyy HH:mm:ss z", // RFC 822, updated by RFC 1123
            "EEEE, dd-MMM-yy HH:mm:ss z",  // RFC 850, obsoleted by RFC 1036
            "EEE MMM d HH:mm:ss yyyy"      // ANSI C's asctime() format
    };

    public static Date parseDate(String time) {
        for (String pattern : DATE_PATTERNS) {
            try {
                SimpleDateFormat df = new SimpleDateFormat(pattern, Locale.US);
                df.setLenient(false);
                df.setTimeZone(GMT);
                return df.parse(time);
            } catch (ParseException ignore) {
            }
        }
        throw new IllegalArgumentException("invalid date format: " + time);
    }

    /**
     * 拆分给定的元素列表字符串（以逗号分隔的标头值）
     * 转换为其组成的非空修剪元素。
     * （RFC2616#2.1：元素列表由逗号和可选的LWS分隔，
     * 并且忽略空元素）。
     *
     * @param list  the element list string
     * @param lower specifies whether the list elements should be lower-cased
     * @return the non-empty elements in the list, or an empty array
     */
    public static String[] splitElements(String list, boolean lower) {
        return split(lower && list != null ? list.toLowerCase(Locale.US) : list, ",", -1);
    }

    /**
     * 将给定字符串拆分为其组成的非空修剪元素，
     * 其由任何给定的分隔符字符来分隔。
     * 这是一个比使用正则表达式更直接、更高效的实现
     * （例如String.split（）），修剪元素并移除空元素。
     *
     * @param str        the string to split
     * @param delimiters the characters used as the delimiters between elements
     * @param limit      if positive, limits the returned array size (remaining of str in last element)
     * @return the non-empty elements in the string, or an empty array
     */
    public static String[] split(String str, String delimiters, int limit) {
        if (str == null)
            return new String[0];
        Collection<String> elements = new ArrayList<String>();
        int len = str.length();
        int start = 0;
        int end;
        while (start < len) {
            for (end = --limit == 0 ? len : start;
                 end < len && delimiters.indexOf(str.charAt(end)) < 0; end++)
                ;
            String element = str.substring(start, end).trim();
            if (element.length() > 0)
                elements.add(element);
            start = end + 1;
        }
        return elements.toArray(new String[0]);
    }

    /**
     * Reads the ISO-8859-1 encoded string starting at the current stream
     * 读取从当前流开始的ISO-8859-1编码字符串
     * position and ending at the first occurrence of the LF character.
     * 位置并在LF字符的第一次出现处结束。
     *
     * @param in the stream from which the line is read
     *           从中读取行的流
     * @return the read string, excluding the terminating LF character
     * 读取的字符串，不包括终止LF字符
     * and (if exists) the CR character immediately preceding it
     * 以及（如果存在）紧挨在它前面的CR字符
     * @throws EOFException if the stream end is reached before an LF character is found
     * @throws IOException  if an IO error occurs, or the line is longer than 8192 bytes
     * @see #readToken(InputStream, int, String, int)
     */
    public static String readLine(InputStream in) throws IOException {
        //todo: update utf-8 by noear, 2021-11-11
        return readToken(in, '\n', "UTF-8", MAX_HEADER_SIZE);
    }

    /**
     * Reads the token starting at the current stream position and ending at
     * 读取从当前流位置开始到结束的令牌
     * the first occurrence of the given delimiter byte, in the given encoding.
     * 给定定界符字节在给定编码中的第一次出现。
     * If LF is specified as the delimiter, a CRLF pair is also treated as one.
     * 如果指定LF作为分隔符，则CRLF对也被视为一对。
     *
     * @param in        the stream from which the token is read 从中读取令牌的流
     * @param delim     the byte value which marks the end of the token, 标记令牌结束的字节值，
     *                  or -1 if the token ends at the end of the stream 或者-1，如果令牌在流的末尾结束
     * @param enc       a character-encoding name
     * @param maxLength the maximum length (in bytes) to read
     * @return the read token, excluding the delimiter
     * @throws UnsupportedEncodingException if the encoding is not supported
     * @throws EOFException                 if the stream end is reached before a delimiter is found
     * @throws IOException                  if an IO error occurs, or the maximum length
     *                                      is reached before the token end is reached
     */
    public static String readToken(InputStream in, int delim,
                                   String enc, int maxLength) throws IOException {
        // note: we avoid using a ByteArrayOutputStream here because it
        // suffers the overhead of synchronization for each byte written
        int b;
        int len = 0; // buffer length
        int count = 0; // number of read bytes
        byte[] buf = null; // optimization - lazy allocation only if necessary
        while ((b = in.read()) != -1 && b != delim) {
            if (count == len) { // expand buffer
                if (count == maxLength)
                    throw new IOException("token too large (" + count + ")");
                len = len > 0 ? 2 * len : 256; // start small, double each expansion
                len = maxLength < len ? maxLength : len;
                byte[] expanded = new byte[len];
                if (buf != null)
                    System.arraycopy(buf, 0, expanded, 0, count);
                buf = expanded;
            }
            buf[count++] = (byte) b;
        }
        if (b < 0 && delim != -1)
            throw new EOFException("unexpected end of stream");
        if (delim == '\n' && count > 0 && buf[count - 1] == '\r')
            count--;
        return count > 0 ? new String(buf, 0, count, enc) : "";
    }

    /**
     * Parses an unsigned long value. This method behaves the same as calling
     * {@link Long#parseLong(String, int)}, but considers the string invalid
     * if it starts with an ASCII minus sign ('-') or plus sign ('+').
     *
     * @param s     the String containing the long representation to be parsed
     * @param radix the radix to be used while parsing s 解析s时要使用的基数
     * @return the long represented by s in the specified radix
     * @throws NumberFormatException if the string does not contain a parsable
     *                               long, or if it starts with an ASCII minus sign or plus sign
     */
    public static long parseULong(String s, int radix) throws NumberFormatException {
        long val = Long.parseLong(s, radix); // throws NumberFormatException
        if (s.charAt(0) == '-' || s.charAt(0) == '+')
            throw new NumberFormatException("invalid digit: " + s.charAt(0));
        return val;
    }

    /**
     * Trims duplicate consecutive occurrences of the given character within the
     * 修剪中给定字符的重复连续出现
     * given string, replacing them with a single instance of the character.
     * 给定字符串，用该字符的单个实例替换它们。
     *
     * @param s the string to trim 要修剪的字符串
     * @param c the character to trim
     * @return the given string with duplicate consecutive occurrences of c
     * replaced by a single instance of c
     */
    public static String trimDuplicates(String s, char c) {
        int start = 0;
        while ((start = s.indexOf(c, start) + 1) > 0) {
            int end;
            for (end = start; end < s.length() && s.charAt(end) == c; end++) ;
            if (end > start)
                s = s.substring(0, start) + s.substring(end);
        }
        return s;
    }

    /**
     * Parses name-value pair parameters from the given "x-www-form-urlencoded"
     * 分析给定“x-www-form-urlencoded”中的名称-值对参数
     * MIME-type string. This is the encoding used both for parameters passed
     * MIME类型字符串。这是用于传递的参数的编码
     * as the query of an HTTP GET method, and as the content of HTML forms
     * 作为HTTP GET方法的查询和HTML表单的内容
     * submitted using the HTTP POST method (as long as they use the default
     * 使用HTTPPOST方法提交（只要他们使用默认的
     * "application/x-www-form-urlencoded" encoding in their ENCTYPE attribute).
     * 其ENCTYPE属性中的“application/x-www-form-urlencoded”编码）。
     * UTF-8 encoding is assumed.
     * <p>
     * The parameters are returned as a list of string arrays, each containing
     * the parameter name as the first element and its corresponding value
     * as the second element (or an empty string if there is no value).
     * <p>
     * The list retains the original order of the parameters.
     *
     * @param s an "application/x-www-form-urlencoded" string
     * @return the parameter name-value pairs parsed from the given string,
     * or an empty list if there are none
     */
    public static List<String[]> parseParamsList(String s) {
        if (s == null || s.length() == 0)
            return Collections.emptyList();
        List<String[]> params = new ArrayList<String[]>(8);
        for (String pair : split(s, "&", -1)) {
            int pos = pair.indexOf('=');
            String name = pos < 0 ? pair : pair.substring(0, pos);
            String val = pos < 0 ? "" : pair.substring(pos + 1);
            try {
                name = URLDecoder.decode(name.trim(), "UTF-8");
                val = URLDecoder.decode(val.trim(), "UTF-8");
                if (name.length() > 0)
                    params.add(new String[]{name, val});
            } catch (UnsupportedEncodingException ignore) {
            } // never thrown
        }
        return params;
    }
    /**
     * Converts a collection of pairs of objects (arrays of size two,
     * each representing a key and corresponding value) into a Map.
     * Duplicate keys are ignored (only the first occurrence of each key is considered).
     * The map retains the original collection's iteration order.
     *
     * @param pairs a collection of arrays, each containing a key and corresponding value
     * @param <K>   the key type
     * @param <V>   the value type
     * @return a map containing the paired keys and values, or an empty map
     */
    @SuppressWarnings("unchecked")
    public static <K, V> Map<K, V> toMap(Collection<? extends Object[]> pairs) {
        if (pairs == null || pairs.isEmpty())
            return Collections.emptyMap();
        Map<K, V> map = new LinkedHashMap<K, V>(pairs.size());
        for (Object[] pair : pairs)
            if (!map.containsKey(pair[0]))
                map.put((K) pair[0], (V) pair[1]);
        return map;
    }
}
